<link rel="import" href="../../bower_components/polymer/polymer-element.html">
<link rel="import" href="../../bower_components/paper-card/paper-card.html">
<link rel="import" href="../../bower_components/paper-dialog/paper-dialog.html">
<link rel="import" href="../../bower_components/paper-dialog-behavior/paper-dialog-behavior.html">
<link rel="import" href="../../bower_components/paper-dialog-scrollable/paper-dialog-scrollable.html">
<link rel="import" href="../../bower_components/iron-ajax/iron-ajax.html">
<link rel="import" href="../../bower_components/paper-toast/paper-toast.html">
<link rel="import" href="../../bower_components/paper-input/paper-input.html">
<link rel="import" href="../../bower_components/paper-input/paper-input-container.html">
<link rel="import" href="../../bower_components/app-route/app-route.html">
<link rel="import" href="../../bower_components/app-route/app-location.html">
<link rel="import" href="shared-styles.html">

<dom-module id="build-view">
    <template>
        <style include="shared-styles"></style>
        <style is="custom-style">
            :host {
                display: block;
                width: 40%;
                height: 100%;
                text-align: center;
                padding-bottom: 32px;
            }

            paper-card {
                width: 100%;
                height: 100%;
            }

            .log {
                text-align: left;
                margin-left: 8px;
                margin-right: 8px;
                margin-top: 0;
            }

            #spinner {
                margin-left: auto;
                margin-right: auto;
                display: block;
            }

            .history {
                width: 100%;
                margin-top: -28px;
            }

            #spinner {
                margin-top: 12px;
            }

            #logDialog {
                position: fixed;
                top: 0;
                left: 0;
                min-width: 80%;
                right: 0;
                margin-left: auto;
                margin-right: auto;
            }

            .line {
                color: var(--primary-color);
            }

            .date {
                color: var(--secondary-color);
            }

            .text {
                word-break: break-all;
                white-space: pre-wrap;
            }

            hr {
                opacity: var(--opacity);
            }

            .empty {
                opacity: var(--opacity);
            }

            .title {
                text-align: left;
                margin-left: 8px;
                margin-bottom: 0px;
            }

            .search {
                text-align: left;
                margin-left: 8px;
                margin-right: 8px;
                padding-top: 0;
            }

            .copy-url {
                margin-top: 8px;
                margin-bottom: 2px;
            }
        </style>

        <h4 class="title">
            Build history

            <template is="dom-if" if="[[!started]]">
                <paper-spinner id="spinner" active></paper-spinner>
            </template>

            <template is="dom-if" if="{{_canDelete(authenticated, started)}}">
                <iron-icon on-click="_clear" class="history icon" icon="icons:clear"></iron-icon>
            </template>
        </h4>

        <app-location route="{{route}}" use-hash-as-path></app-location>
        <app-route route="{{route}}" pattern="/:repository/:branch/:buildId" data="{{routeData}}"
                   tail="{{subroute}}"></app-route>

        <paper-input-container class="search">
            <label slot="label">filter</label>
            <iron-input on-keydown="_onSearchFilter" autofocus slot="input" bind-value="{{query}}">
                <input autofocus>
            </iron-input>
        </paper-input-container>

        <template is="dom-repeat" items="{{_filter(builds, query)}}" as="build">
            <build-card on-click="_onClick" build="[[build]]"></build-card>
        </template>

        <template is="dom-if" if="{{_isEmpty(builds)}}">
            <span class="empty">empty</span>
        </template>

        <paper-dialog id="logDialog" with-backdrop modal>
            <h2>[[_getHeader(build)]]</h2>
            <iron-icon on-click="_copyUrl" class="copy-url icon" icon="icons:content-copy"></iron-icon>
            <iron-icon on-click="_queueAnother" class="copy-url icon" icon="icons:cached"></iron-icon>
            <template is="dom-if" if="{{_isCancellable(build)}}">
                <iron-icon on-click="_cancel" class="copy-url icon" icon="icons:cancel"></iron-icon>
            </template>
            <paper-dialog-scrollable id="terminal" class="log">
                <paper-spinner id="spinner" active="[[loading]]"></paper-spinner>

                <template is="dom-repeat" items="{{log}}" as="event">
                    <span class="line">[{{_line()}}]</span>
                    <span class="date">[[_toDate(event.time)]]</span>
                    <span class="text">[[_parseLine(event.line)]]</span>
                    <hr>
                </template>
            </paper-dialog-scrollable>
            <div class="buttons">
                <paper-button dialog-dismiss on-click="_close">Close</paper-button>
            </div>
        </paper-dialog>

        <paper-toast id="toast" text="[[errorText]]"></paper-toast>

        <iron-ajax id="logLoader" url="/api/" method="post" handle-as="json" on-response="_onBuildLog"></iron-ajax>
        <iron-ajax id="clearBuilds" url="/api/" method="post" handle-as="json" on-response="_onClear"></iron-ajax>
        <iron-ajax id="statusLoader" url="/api/" method="post" handle-as="json"
                   on-response="_onBuildsDownloaded"></iron-ajax>
        <iron-ajax id="cancelBuild" url="/api/" method="post" handle-as="json"
                   on-response="_onBuildCancelled"></iron-ajax>

    </template>
</dom-module>
<script>
    class BuildView extends Polymer.Element {

        static get is() {
            return 'build-view';
        }

        openDialog() {
            this.$.dialog.open();
        }

        constructor() {
            super();
            this.LOG_POLL = 2500;
            this.started = false;
            this.authenticated = false;
            this.build = null;
            this.builds = [];
            this.log = [];
            this.query = '';

            application.onAuthenticated(() => {
                this.authenticated = true;

                setTimeout(() => {
                    if (this.routeData.buildId) {
                        this._openBuildLog(this.routeData.buildId);
                    }
                }, 1);
            });

            application.onLogout(() => {
                this.authenticated = false;
            });

            application.subscribe('onFiltersChanged', (e) => {
                this.set('routeData.repository', e.repository);
                this.set('routeData.branch', e.branch);
                this._load();
            });
        }

        ready() {
            super.ready();

            setInterval(() => {
                if (this.buildId != null) {
                    this._reload();
                }
            }, this.LOG_POLL);

            setInterval(() => {
                this._load();
            }, 2500);

            this._load();
        }

        _filter(builds, query) {
            let results = [];

            if (query.length > 0) {
                let matcher = new RegExp(query, 'i');
                for (let build of builds) {
                    let repo = matcher.test(build.config.repository);
                    let branch = matcher.test(build.config.branch);
                    let message = matcher.test(build.message);
                    let author = matcher.test(build.author);
                    let cmdLine = matcher.test(build.config.cmdLine);

                    if (repo || branch || message || author || cmdLine) {
                        results.push(build);
                    }
                }
            } else {
                results = builds;
            }
            return results;
        }

        _copyUrl() {
            navigator.clipboard.writeText(window.location)
                .then(() => {
                    this._showToast("Copied url to clipboard.");
                })
                .catch(() => {
                    this._showToast("Failed to copy url to clipboard.");
                });
        }

        _queueAnother(e) {
            application.publish('schedule', this.build.config.id);
            this._showToast("Scheduled another build.");
        }

        _cancel(e) {
            this.$.cancelBuild.body = JSON.stringify({
                token: application.token,
                target: 'builds',
                route: 'cancel',
                id: this.build.id
            });
            this.$.cancelBuild.generateRequest();
        }

        _onBuildCancelled(event, request) {
            let status = request.response.status;

            if (status === 'ACCEPTED') {
                this._showToast('This build has been marked for cancellation.');
            } else {
                this._showToast('Failed to cancel the build, has it completed already?');
            }
        }

        _isCancellable() {
            if (this.build) {
                let status = this.build.progress;
                return (status === 'QUEUED' || status === 'CLONING' || status === 'BUILDING');
            } else {
                return false;
            }
        }

        _onSearchFilter(e) {
            if (e.keyCode === 13) {
                this.query = '';
            }
        }

        _getHeader(build) {
            if (build) {
                let config = build.config;
                if (config.branch) {
                    return build.message;
                } else {
                    if (config.repositoryName) {
                        return config.repositoryName;
                    } else {
                        return config.cmdLine.substring(0, Math.min(48, config.cmdLine.length));
                    }
                }
            }
        }

        _canDelete() {
            return this.started && application.role('admin');
        }

        _onClear(event, request) {
            this.builds = request.response.list;
        }

        _onBuildsDownloaded(event, request) {
            this.started = true;
            this.builds = request.response.list;

            // update the build information after opening the log.
            if (this.builds) {
                for (let build of this.builds) {
                    if (build.id === this.buildId) {
                        this.set('build', build);
                    }
                }
            }
        }

        _load() {
            this.$.statusLoader.body = JSON.stringify({
                token: application.token,
                target: 'builds',
                route: 'list',
                repository: this.routeData.repository,
                branch: this.routeData.branch
            });
            this.$.statusLoader.generateRequest();
        }

        _parseLine(line) {
            return line;
        }

        _isEmpty() {
            return this.builds.length === 0;
        }

        _onClick(e) {
            if (this._canViewLog()) {
                this.build = e.model.build;
                this.set('route.path', this.route.path + this.build.id);
                this._openBuildLog(this.build.id);
            } else {
                this._showToast("You are not authorized to view the log.");
            }
        }

        _showToast(text) {
            this.errorText = text;
            this.$.toast.show();
        }

        _openBuildLog(buildId) {
            // try to find the build if its already loaded.
            if (this.build === null && this.builds) {
                for (let build of this.builds) {
                    if (build.id === buildId) {
                        this.set('build', build);
                    }
                }
            }
            this.buildId = buildId;
            this.loading = true;
            this.line = 0;
            this.offset = 0;
            this.log = [];
            this.$.logDialog.open();
            this._reload();
        }

        _canViewLog() {
            return this.started && (application.role('user') || application.role('admin'));
        }

        _toDate(epochMS) {
            return new Date(epochMS).toLocaleString();
        }

        _onBuildLog(event, request) {
            let events = request.response.collection;

            // on first load show all items at once.
            if (this.offset === 0) {
                this.log.push(...events);
                this._updateLog();
            } else {
                // delay each lines appearance by its timestamp plus the polling interval.
                // simulates real-time logging to screen.
                let current = this.build;
                for (let i in events) {
                    let event = events[i];
                    setTimeout(() => {
                        if (this.build != null && current.id === this.build.id) {
                            this.log.push(event);
                            this._updateLog();
                        }
                    }, this.LOG_POLL - (new Date().getTime() - event.time));
                }
            }

            if (events.length > 0) {
                this.offset = events[events.length - 1].time;
            }
            this.loading = false;
        }

        _updateLog() {
            let terminal = this.$.terminal.$.scrollable;
            let autoscroll = (terminal.scrollHeight - terminal.scrollTop === terminal.clientHeight);

            this.notifySplices('log');
            this.$.logDialog.notifyResize();
            setTimeout(() => {
                if (autoscroll) {
                    this.$.terminal.$.scrollable.scrollTop = Number.MAX_SAFE_INTEGER;
                }
            }, 1);
        }

        _clear() {
            this.$.clearBuilds.body = JSON.stringify({
                token: application.token,
                target: 'builds',
                route: 'clear'
            });
            this.$.clearBuilds.generateRequest();
        }

        _reload() {
            this.$.logLoader.body = JSON.stringify({
                token: application.token,
                id: this.buildId,
                offset: this.offset,
                target: 'builds',
                route: 'log'
            });
            this.$.logLoader.generateRequest();
        }

        _line() {
            return this.line++;
        }

        _close() {
            this.set('routeData.buildId', null);
            this.buildId = null;
        }
    }

    window.customElements.define(BuildView.is, BuildView);


</script>
